package com.dq.controller;


import com.dq.controller.client.Client;
import com.dq.controller.client.Md5Object;
import com.dq.controller.client.Messages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * Created by EDZ on 2018/3/17.
 * @author ZHANGZELI
 */
@Controller
public class HelloDigestRfc {


    private static final Logger LOGGER = LoggerFactory.getLogger(HelloDigestRfc.class);

    /** 保护的质量，这个参数规定服务器支持哪种保护方案.
     *  客户端可以从列表中选择一个值
     *  “auth”表示只进行身份查验，
     *  “auth-int”表示进行查验外，还有一些完整性保护。
     *  需要看更详细的描述，请参阅RFC2617；
     *  **/
    public static final String QOP = "auth-int";

    /**一个不透明的（不让外人知道其意义）数据字符串，这些数据请求使用同一保护空间内的url。**/
    private String opaque = "bHhWSTU2RjFMakZ0c3lTcGJF";

    /**
     * 显示给用户的字符串，这样用户就知道可以用那个用户名和密码了。
     * 至少包含主机名，还可能包含用户集合
     **/
    private String realm = "zhangzeli@host.com";

    /** 用户名**/
    private String userName="zhangzeli";

    /** 密码**/
    private String upassword="123456";


    @RequestMapping("/digest")
    @ResponseBody
    public Object digesRfc(HttpServletRequest request, HttpServletResponse response,@RequestBody(required = false) String jsonBody){
        String authorization = request.getHeader("Authorization");
        //请求方式
        String method = request.getMethod();

        if(StringUtils.isEmpty(authorization)){
            LOGGER.info("没有请求头信息，服务端进行授权");
            setHeadParams(request,response);
            return new Messages("needauth","授权认证");
        }else{
            String auth = authorization.substring(7, authorization.length());
            LOGGER.info("开始认证 ： "+auth);
            Map<String,String> mapByKeyArray = Client.getMapByKeyArray(auth.split(","));
            //mapByKeyArray.forEach((k,v)-> LOGGER.info("key:value    ---》"+k+":"+v));
            String cusername = mapByKeyArray.get("username");
            String nc = mapByKeyArray.get("nc");
            String cnonce = mapByKeyArray.get("cnonce");
            String uri = mapByKeyArray.get("uri");
            String qop = mapByKeyArray.get("qop"); 
            String cresponse = mapByKeyArray.get("response");
            String nonce = mapByKeyArray.get("nonce");
            //如果客户端之前没有获取过nonce
            String renonce = (String) request.getSession().getAttribute(mapByKeyArray.get("nonce"));
            if(StringUtils.isEmpty(nonce)){
                setHeadParams(request,response);
                LOGGER.info("不合法的请求");
                return new Messages("needauth","不合法的请求");
            }
            //计算HA1
            String hA1 = Md5Object.encrypt(cusername + ":"+ realm+ ":" + upassword);
            LOGGER.info("HA1：[" +cusername + ":"+ realm+ ":" + upassword+"]-->["+hA1+"]");

            //计算HA2和response
            String hA2 ;
            //如果 qop 值为“auth”或未指定
            if(qop==null||!qop.contains(QOP)){
                hA2 = Md5Object.encrypt(method+ ":" +uri);
                LOGGER.info("HA2:["+method+ ":" +uri+"]--->[" +hA2 +"]");
            }else{
                if(!StringUtils.isEmpty(jsonBody)){
                    jsonBody = Md5Object.encrypt(jsonBody);
                    hA2 = Md5Object.encrypt(method+ ":" +uri+":"+jsonBody);
                    LOGGER.info("HA2:["+method+ ":" +uri+":"+jsonBody+"]--->[" +hA2 +"]");
                }else{
                    hA2 = Md5Object.encrypt(method+ ":" +uri);
                    LOGGER.info("HA2:["+method+ ":" +uri+"]--->[" +hA2 +"]");
                }
            }

            //计算response
            String sresponse;
            //只要qop为auth或auth-int，就要使用这种方式。
            // 它向摘要中添加了随机计数、qop和cnonce数据
            if(qop==null||!qop.contains("auth")){
                 sresponse = Md5Object.encrypt(hA1 + ":" + nonce + ":" + hA2);
                 LOGGER.info("sresponse:["+hA1 + ":" + nonce + ":" + hA2+"]-->["+sresponse+"]");
            }else{
                 sresponse = Md5Object.encrypt(hA1 + ":" + nonce + ":"+ nc + ":" + cnonce + ":" + qop + ":" + hA2);
                 LOGGER.info("sresponse:["+hA1 + ":" + nonce + ":"+ nc + ":" + cnonce + ":" + qop + ":" + hA2+"]-->["+sresponse+"]");
            }

            LOGGER.info("客户端摘要"+cresponse);
            if(sresponse.equals(cresponse)){
                LOGGER.info("认证成功");
                return new Messages("success","认证成功");
            }else{
                setHeadParams(request,response);
                LOGGER.info("认证失败");
                return new Messages("reneedauth","认证失败需要重新认证");
            }
        }
    }

    private void setHeadParams(HttpServletRequest request,HttpServletResponse response){
        LOGGER.info("-------------------------------------------");
        response.setStatus(401);
        response.setHeader("Cache-Control", "no-store");
        response.setDateHeader("Expires", 0);
        //随机生成 base64，每次401都不一样
        String nonce = Md5Object.getNonce();
        StringBuffer sb = new StringBuffer();
        sb.append("realm=").append(realm).append(",")
                .append("qop=").append(Client.QOP).append(",")
                .append("nonce=").append(nonce).append(",")
                // 服务器产生的由客户下去请求时原样返回。最好是Base64串或十六进制字符串
                .append("opaque=").append(opaque);
        response.setHeader("WWW-Authenticate","Digest "+sb.toString());
        //因为每次请求服务端401后生成的随机数都不一样,
        // 这里可以保存起来不接受重复的nonce,暂时放在session中,
        // 如果不要session,可以考虑cache和redis
        request.getSession().setAttribute(nonce,nonce);
    }
}
